﻿// The dropbox client was the first one, and as the code was refactored out, we have yet to update the original (we will do that)

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading.Tasks;

using System.Net.Http;
using Windows.Security.Authentication.Web;

#if WINRT
using System.Net.Http.Headers;
using Windows.Storage.Streams;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using System.Runtime.InteropServices.WindowsRuntime;
#endif
using System.IO;
using SimpleJson;
using System.Net.Shared;

namespace Dropbox
{
    public enum DropboxClientModes
    {
        AppFolder,
        Dropbox
    }
    public enum DropboxThumbNailFormats
    {
        jpeg,
        png
    }
    public enum DropboxThumbNailSizes 
    {
        /// <summary>
        /// 32x32
        /// </summary>
        Small, 
        /// <summary>
        /// 64x64
        /// </summary>
        Medium,
        /// <summary>
        /// 128x128
        /// </summary>
        Large, 
        /// <summary>
        /// 64x64
        /// </summary>
        S,
        /// <summary>
        /// 128x128
        /// </summary>
        M,
        /// <summary>
        /// 640x480
        /// </summary>
        L,
        /// <summary>
        /// 1024x768
        /// </summary>
        XL

    }

    public abstract class DropBoxException : Exception {
        
    }

    public class DropboxBadOAuthParametersException : Exception { public DropboxBadOAuthParametersException(string message):base(message) {} }
    public class DropboxBadOrExpiredTokenReAuthException : Exception { public DropboxBadOrExpiredTokenReAuthException(string message):base(message) {} }
    public class DropboxBadOAuthRequestException : Exception { public DropboxBadOAuthRequestException(string message):base(message) {} }
    public class DropboxFileOrFolderNotFoundException : Exception { public DropboxFileOrFolderNotFoundException(string message):base(message) {} }
    public class DropboxBadHttpVerbException : Exception { public DropboxBadHttpVerbException(string message):base(message) {} }
    public class DropboxTooManyRequestsException : Exception { public DropboxTooManyRequestsException(string message):base(message) {} }
    public class DropboxStorageExceededException : Exception { public DropboxStorageExceededException(string message):base(message) {} }
    public class DropboxServerErrorException : Exception { public DropboxServerErrorException(string message) : base(message) { } }


    public partial class DropboxClient
    {

        const int API_VERSION = 1;
        const string SECURE_API_HOST = "https://api.dropbox.com";
        const string NOTSECURE_API_HOST = "http://api.dropbox.com";
        const string WEB_HOST = "https://www.dropbox.com";
        const string SECURE_API_CONTENT_HOST = "https://api-content.dropbox.com";
        const string NOTSECURE_API_CONTENT_HOST = "http://api-content.dropbox.com";

        public string AppKey { get; set; }
        public string AppSecret { get; set; }
        public string Locale { get; set; }
        public DropboxOAuthToken RequestToken { get; set; }
        public DropboxOAuthToken AccessToken { get; set; }
        public DropboxClientModes ClientMode { get; set; }

        protected String rootFolderName
        {
            get
            {
                return ClientMode == DropboxClientModes.AppFolder?"sandbox":"dropbox";
            }
        }

        public DropboxClient(string appKey, string appSecret, string accessToken = "", DropboxClientModes clientMode = DropboxClientModes.AppFolder, string locale = "")
        {
            AppKey = appKey;
            AppSecret = appSecret;
            Locale = locale;
            RequestToken = null;
            if (String.IsNullOrEmpty(accessToken))
                AccessToken = null;
            else
                AccessToken = new DropboxOAuthToken(accessToken);
            ClientMode = clientMode;
        }

        #region oAuth setup

        /// <summary>
        /// Build OAuth access headers for a future request
        /// </summary>
        /// <param name="method">The HTTP method being used (e.g. 'GET' or 'POST').</param>
        /// <param name="resource_url">The full url the request will be made to.</param>
        /// <param name="parms">A dictionary of parameters to add to what's already on the url.
        ///                     Typically, this would consist of POST parameters.</param>
        /// <param name="request_token"></param>
        /// <returns>
        /// A tuple of (header_dict, params) where header_dict is a dictionary
        /// of header names and values appropriate for passing into dropbox.rest.RESTClient
        /// and params is a dictionary like the one that was passed in, but augmented with
        /// oauth-related parameters as appropriate.
        /// </returns>
        /// <remarks>
        /// This is a partial port of a method from the DropBox Python API modified using info from
        /// http://dkdevelopment.net/2010/05/18/dropbox-api-and-restsharp-for-a-c-developer/ 
        /// (which is a post on using oAuthBase with RestSharp)
        /// </remarks>
        private Dictionary<String, String> buildAccessHeaders(string method, string resource_url, Dictionary<String, String> parms = null, bool useHMACSHA1=false, DropboxOAuthToken request_token = null)
        {           
            if (!String.IsNullOrEmpty(Locale) && !parms.ContainsKey("locale"))
                parms["locale"] = Locale;

            var objToken = request_token??RequestToken??AccessToken;

            String token = "";
            String secret = "";
            if(objToken != null)
            {
                token = objToken.Token;
                secret = objToken.Secret;
            }
                       
            // Add additional oAuth Parms that will need to be included in the signature
            parms["oauth_consumer_key"] = AppKey;
            if(objToken != null)
                parms["oauth_token"] = token;
            //if (useHMACSHA1)
            //{
                string nonce = oAuthGenerateNonce();
                parms["oauth_nonce"] = nonce;
            //}
            //if (useHMACSHA1)
            //{
                string timeStamp = oAuthGenerateTimeStamp();
                parms["oauth_timestamp"] = timeStamp;
            //}
            parms["oauth_signature_method"] = useHMACSHA1 ? "HMAC-SHA1" : "PLAINTEXT";
            //parms["oauth_version"] = "1.0";

            if (!useHMACSHA1)
            {
                parms["oauth_signature"] = string.Format("{0}&{1}", urlEncode(AppSecret), urlEncode(secret)); // urlDecode("%26");
            }
            else
            {
                throw new NotImplementedException("SHA1 Hasing not used");
                //
                // Compute base signature string and sign it.
                //    This is a common operation that is required for all requests even after the token is obtained.
                //    Parameters need to be sorted in alphabetical order
                //    Keys and values should be URL Encoded.
                //
/*
                var qsStringParams = new List<String>();

                foreach (var kvp in parms)
                {
                    var strItem = String.Format("{0}={1}", urlEncode(kvp.Key), urlEncode(kvp.Value));
                    qsStringParams.Add(strItem);
                }

                var qsString = String.Join("&", qsStringParams.OrderBy(i => i).ToArray());

                string normalizedUrl;
                string normalizedRequestParameters;

                String SigBaseStringParams = qsString;
                String SigBaseString = method.ToUpper() + "&";

                SigBaseString += urlEncode(resource_url) + "&" + urlEncode(SigBaseStringParams);

                IBuffer KeyMaterial = CryptographicBuffer.ConvertStringToBinary(string.Format("{0}&{1}", urlEncode(AppSecret), urlEncode(secret)), BinaryStringEncoding.Utf8);
                MacAlgorithmProvider HmacSha1Provider = MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA1");
                CryptographicKey MacKey = HmacSha1Provider.CreateKey(KeyMaterial);
                IBuffer DataToBeSigned = CryptographicBuffer.ConvertStringToBinary(SigBaseString, BinaryStringEncoding.Utf8);
                IBuffer SignatureBuffer = CryptographicEngine.Sign(MacKey, DataToBeSigned);
                String sig = CryptographicBuffer.EncodeToBase64String(SignatureBuffer);

                parms["oauth_signature"] = sig;
 */ 
            }

            return parms;

        }

        private String urlEncode(String value)
        {
            return WebUtility.UrlEncode(value);
        }

        private String urlDecode(String value)
        {
            return WebUtility.UrlDecode(value);
        }

        private StringBuilder dictionaryToQueryStringBuilder(Dictionary<String, String> parms)
        {
            var qsString = new StringBuilder();
            foreach (var parm in parms)
            {
                qsString.AppendFormat("{0}{1}={2}", qsString.Length == 0 ? "" : "&", urlEncode(parm.Key), urlEncode(parm.Value));
            }
            return qsString;
        }
        #endregion

        #region borrowed (and modified) from oAuthBase
        /// <summary>
        /// Generate the timestamp for the signature        
        /// </summary>
        /// <returns></returns>
        public virtual string oAuthGenerateTimeStamp()
        {
            // Default implementation of UNIX time of the current UTC time
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        protected Random random = new Random();

        /// <summary>
        /// Generate a nonce
        /// </summary>
        /// <returns></returns>
        public virtual string oAuthGenerateNonce()
        {
            // Just a simple implementation of a random number between 123400 and 9999999
            return random.Next(123400, 9999999).ToString();
        }

        const string OAuthVersion = "1.0";
        #endregion

        #region Http Stuff
        public HttpStatusCode _lastResultCode = HttpStatusCode.Unused;
        public string _lastErrorReason = "";
        public bool Unchanged
        {
            get
            {
                return ((int)_lastResultCode) == 304;
            }
        }

        private string fixBaseUrl(string url)
        {
            var pieces = url.Split('/');

            for (var i = 2; i < pieces.Length; i++ )
                pieces[i] = urlEncode(pieces[i]).Replace("+", "%20");

            return string.Join("/", pieces);
        }

        private Task<string> simpleHttpPost(string url, Dictionary<string, string> parms)
        {
            var parmsPlusOauthQS = dictionaryToQueryStringBuilder(buildAccessHeaders("POST", url, parms)).ToString();
            return simpleHttpPost(url + "?" + parmsPlusOauthQS);
        }
        
        private async Task<string> simpleHttpPost(string url)
        {
            Debug.WriteLine("post url:" + url);
            var result = await HttpRequestHelper.simpleHttpPost(url);
            _lastResultCode = HttpRequestHelper._lastResultCode;
            _lastErrorReason = HttpRequestHelper._lastErrorReason;
            return result;
        }

        private Task<string> simpleHttpGet(string url, Dictionary<string, string> parms)
        {

            var parmsPlusOauthQS = dictionaryToQueryStringBuilder(buildAccessHeaders("GET", url, parms)).ToString();
            return simpleHttpGet(url + "?" + parmsPlusOauthQS);
        }
        
        private async Task<string> simpleHttpGet(string url)
        {
            Debug.WriteLine("get url:" + url);
            var result = await HttpRequestHelper.simpleHttpGet(url);
            _lastResultCode = HttpRequestHelper._lastResultCode;
            _lastErrorReason = HttpRequestHelper._lastErrorReason;
            return result;
        }
                
        private Task<bool> simpleHttpSaveContent(string url, Dictionary<string, string> parms, Func<Stream> getStreamFunc)
        {
            var fixedUrl = fixBaseUrl(url);
            var parmsPlusOauthQS = dictionaryToQueryStringBuilder(buildAccessHeaders("GET", fixedUrl, parms)).ToString();
            return simpleHttpSaveContent(fixedUrl + "?" + parmsPlusOauthQS, getStreamFunc);
        }
        private async Task<bool> simpleHttpSaveContent(string url, Func<Stream> getStreamFunc)
        {
            Debug.WriteLine("save content url:" + url);
            var result = await HttpRequestHelper.simpleHttpGetSavedToStream(url, getStreamFunc);
            _lastResultCode = HttpRequestHelper._lastResultCode;
            _lastErrorReason = HttpRequestHelper._lastErrorReason;
            return result;
        }
        private Task<byte[]> simpleHttpGetByteContent(string url, Dictionary<string, string> parms)
        {
            var fixedUrl = fixBaseUrl(url);
            var parmsPlusOauthQS = dictionaryToQueryStringBuilder(buildAccessHeaders("GET", fixedUrl, parms)).ToString();
            return simpleHttpGetByteContent(fixedUrl + "?" + parmsPlusOauthQS);
        }

        private async Task<byte[]> simpleHttpGetByteContent(string url)
        {
            Debug.WriteLine("get content url:" + url);
            var result = await HttpRequestHelper.simpleHttpGetBuffer(url);
            _lastResultCode = HttpRequestHelper._lastResultCode;
            _lastErrorReason = HttpRequestHelper._lastErrorReason;
            return result;
        }

        private Task<string> simpleHttpPut(string url, Dictionary<string, string> parms, Byte[] fileContents)
        {
            var parmsPlusOauthQS = dictionaryToQueryStringBuilder(buildAccessHeaders("PUT", url, parms)).ToString();
            return simpleHttpPut(url + "?" + parmsPlusOauthQS, fileContents);
        }

        private async Task<string> simpleHttpPut(string url, byte[] fileContents )
        {
            Debug.WriteLine("put url:" + url);
            var result = await HttpRequestHelper.simpleHttpPut(url, fileContents);
            _lastResultCode = HttpRequestHelper._lastResultCode;
            _lastErrorReason = HttpRequestHelper._lastErrorReason;
            return result;
        }

        private Task<string> simpleHttpPut(string url, Dictionary<string, string> parms, Stream fileContents)
        {
            var parmsPlusOauthQS = dictionaryToQueryStringBuilder(buildAccessHeaders("PUT", url, parms)).ToString();
            return simpleHttpPut(url + "?" + parmsPlusOauthQS, fileContents);
        }

        private async Task<string> simpleHttpPut(string url, Stream fileContents)
        {
            Debug.WriteLine("put url:" + url);
            var result = await HttpRequestHelper.simpleHttpPut(url, fileContents);
            _lastResultCode = HttpRequestHelper._lastResultCode;
            _lastErrorReason = HttpRequestHelper._lastErrorReason;
            return result;
        }
        protected bool lastStatusWasOK(HttpStatusCode[] additionalOkStatuses = null)
        {
            return _lastResultCode == HttpStatusCode.OK || ((int)_lastResultCode < 400) || (additionalOkStatuses != null && additionalOkStatuses.Contains(_lastResultCode));
        }

        public Exception LastStatusCodeAsError
        {
            get
            {
                switch (_lastResultCode)
                {
                    case HttpStatusCode.BadRequest:
                        return new DropboxBadOAuthParametersException("Bad Parameter");
                        break;
                    case HttpStatusCode.Unauthorized:
                        return new DropboxBadOrExpiredTokenReAuthException("Bad or expired Access Token");
                        break;
                    case HttpStatusCode.Forbidden:
                        return new DropboxBadOAuthRequestException("Bad oAuth Request");
                        break;
                    case HttpStatusCode.NotFound:
                        return new DropboxFileOrFolderNotFoundException("File or folder not found.");
                        break;
                    case HttpStatusCode.MethodNotAllowed:
                        return new DropboxBadHttpVerbException("Request used POST or GET when it should have used the other method.");
                        break;
                    case HttpStatusCode.ServiceUnavailable:
                        return new DropboxTooManyRequestsException("Too many requests are being received by this app.. you are being limited.");
                        break;
                }
                if((int)_lastResultCode == 507)
                    return new DropboxStorageExceededException ("User is over Dropbox storage quota.");
                else if ((int)_lastResultCode>499)
                    return new DropboxServerErrorException("Dropbox is offline.. check dropbox ops.");
                return null;
            }
        }

        #endregion
        
        #region oAuth methods
        public bool IsAuthenticated
        {
            get
            {
                return AccessToken != null && !String.IsNullOrEmpty(AccessToken.Token) && !String.IsNullOrEmpty(AccessToken.Secret);
            }
        }
        public bool IsGettingAuthTicket
        {
            get
            {
                return RequestToken != null && AccessToken == null;
            }
        }

        public async Task<DropboxOAuthToken> GetRequestToken()
        {
            var baseurl = SECURE_API_HOST + @"/1/oauth/request_token";
            String result = await simpleHttpPost(baseurl, new Dictionary<string, string>());

            RequestToken = new DropboxOAuthToken(result);
#if DEBUG
            Debug.WriteLine("Request Token: " + RequestToken.ToString());
#endif
            return RequestToken;
        }

        public async Task<string> GetAuthorizeUri(string callbackUrl = "",  bool throwErrorOnNotAuthed = true)
        {
            if (RequestToken == null)
            {
                var token = await GetRequestToken(); // Ignoring the return result as it will be in the class variable
            }
            if (RequestToken != null)
            {
                return "https://www.dropbox.com/1/oauth/authorize?oauth_token=" + RequestToken.Token + (String.IsNullOrEmpty(callbackUrl) ? "" : "&oauth_callback=" + urlEncode(callbackUrl)) + (String.IsNullOrEmpty(Locale) ? "" : ("&locale=" + Locale));
            }
            if(throwErrorOnNotAuthed)
                throw new UnauthorizedAccessException();
            return "";
        }

        public async Task<bool> Authorize(string endUrl = "")
        {
            if (IsAuthenticated)
            {
                if (await checkAuthenticationToken())
                    return true;
                else
                    AccessToken = null;
            }

            try
            {
                String DropBoxUrl = await GetAuthorizeUri(endUrl);

                System.Uri StartUri = new Uri(DropBoxUrl);

                System.Uri EndUri = null;
                if(!String.IsNullOrEmpty(endUrl))
                    EndUri = new Uri(endUrl);

                Debug.WriteLine("Navigating to: " + DropBoxUrl);

                WebAuthenticationResult WebAuthenticationResult;
                if(!String.IsNullOrEmpty(endUrl))
                    WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
                                                        WebAuthenticationOptions.None,
                                                        StartUri, 
                                                        EndUri);
                else
                    WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
                                                        WebAuthenticationOptions.None,
                                                        StartUri); //, 
                //EndUri);
                if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success ||
                    (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.UserCancel && WebAuthenticationResult.ResponseData != null))
                {
                    Debug.WriteLine(WebAuthenticationResult.ResponseData.ToString());
                    var accessToken = await GetAccessToken();
                    return true;
                }
                else if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
                {
                    Debug.WriteLine("HTTP Error returned by AuthenticateAsync() : " + WebAuthenticationResult.ResponseErrorDetail.ToString());
                    return false;
                }
                else
                {
                    Debug.WriteLine("Error returned by AuthenticateAsync() : " + WebAuthenticationResult.ResponseStatus.ToString());
                    return false;
                }

            }
            catch (Exception ex)
            {
                return false;
            }
            //return true;
        }

        private async Task<bool> checkAuthenticationToken()
        {
            try
            {
                var accInfo = await GetAccountInfo();
                return (accInfo != null);

            }
            catch (Exception ex)
            {
            }
            return false;
        }
        public async Task<DropboxOAuthToken> GetAccessToken(DropboxOAuthToken requestToken = null)
        {
            if(requestToken != null)
                RequestToken = requestToken;

            var baseurl = SECURE_API_HOST + @"/1/oauth/access_token";

            String result = await simpleHttpPost(baseurl, new Dictionary<string, string>());

            AccessToken = new DropboxOAuthToken(result);
            RequestToken = null; // need to clear the request token (it is no longer needed)

            // At this point it would be a good thing to save this so the user doesn't have to do this again...
            return AccessToken;
        }
    #endregion

        public async Task<DropboxAccountInfo> GetAccountInfo(DropboxOAuthToken accessToken = null)
        {
            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/account/info";

            String result = await simpleHttpPost(baseurl, new Dictionary<string, string>());
            if (!lastStatusWasOK())
                return null;

            return DropboxAccountInfo.Load(result);
        }

        #region File/Dir methods

        public async Task<bool> SaveFileToStream(String path, Func<Stream> getStreamFunc, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);
            
            if (accessToken != null)
                AccessToken = accessToken;


            var baseurl = SECURE_API_CONTENT_HOST + String.Format(@"/1/files/{0}/{1}", rootFolderName, path);
            var result =await simpleHttpSaveContent(baseurl, new Dictionary<string,string>(), getStreamFunc);
            
            if (!lastStatusWasOK())
                return false;

            return result;
        }

        public async Task<byte[]> GetFile(String path, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_CONTENT_HOST + String.Format(@"/1/files/{0}/{1}", rootFolderName, path);
            byte[] buffer = await simpleHttpGetByteContent(baseurl, new Dictionary<string,string>());
            if (!lastStatusWasOK())
                return null;

            return buffer;

        }
        public async Task<byte[]> GetThumbnail(String path, DropboxThumbNailFormats format = DropboxThumbNailFormats.jpeg, DropboxThumbNailSizes size = DropboxThumbNailSizes.Small, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_CONTENT_HOST + String.Format(@"/1/thumbnails/{0}/{1}", rootFolderName, path);
            var parms = new Dictionary<String, String>();

            if (format != DropboxThumbNailFormats.jpeg)
                parms["format"] = format.ToString().ToLower();
            if (size != DropboxThumbNailSizes.Small)
                parms["size"] = size.ToString().ToLower();

            var buffer = await simpleHttpGetByteContent(baseurl, parms);

            if (!lastStatusWasOK())
                return null;

            return buffer;
        }
        public async Task<bool> SaveThumbnailToStream(String path, Func<Stream> getStreamFunc, DropboxThumbNailFormats format = DropboxThumbNailFormats.jpeg, DropboxThumbNailSizes size = DropboxThumbNailSizes.Small, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_CONTENT_HOST + String.Format(@"/1/thumbnails/{0}/{1}", rootFolderName, path);
            var parms = new Dictionary<String, String>();

            if (format != DropboxThumbNailFormats.jpeg)
                parms["format"] = format.ToString().ToLower();
            if (size != DropboxThumbNailSizes.Small)
                parms["size"] = size.ToString().ToLower();

            var result = await simpleHttpSaveContent(baseurl, new Dictionary<string, string>(), getStreamFunc);

            if (!lastStatusWasOK())
                return false;

            return true;
        }

        public async Task<DropboxFileSystemItem> PutFile(Stream Data, String pathAndName, bool overwrite = false, String revisionTo = "", DropboxOAuthToken accessToken = null)
        {
            pathAndName = (pathAndName.StartsWith(@"/") ? pathAndName.Substring(1) : pathAndName);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_CONTENT_HOST + String.Format(@"/1/files/{0}/{1}", rootFolderName, pathAndName);
            var parms = new Dictionary<String, String>();

            parms["overwrite"] = overwrite.ToString();
            if (!String.IsNullOrEmpty(revisionTo))
                parms["parent_rev"] = revisionTo;
               
            String result = await simpleHttpPut(baseurl, parms, Data);
            return DropboxFileSystemItem.Load(result);
        }
        public async Task<DropboxFileSystemItem> PutFile(Byte[] Data, String pathAndName, bool overwrite = false, String revisionTo = "", DropboxOAuthToken accessToken = null)
        {
            pathAndName = (pathAndName.StartsWith(@"/") ? pathAndName.Substring(1) : pathAndName);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_CONTENT_HOST + String.Format(@"/1/files_put/{0}/{1}", rootFolderName, pathAndName);
            var parms = new Dictionary<String, String>();

            parms["overwrite"] = overwrite.ToString();
            if (!String.IsNullOrEmpty(revisionTo))
                parms["parent_rev"] = revisionTo;
               
            String result = await simpleHttpPut(baseurl, parms, Data);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileSystemItem.Load(result);
        }
        public async Task<DropboxFileSystemItem> PostFile(Stream Data, String pathAndName, bool overwrite = false, String revisionTo = "", DropboxOAuthToken accessToken = null)
        {
            pathAndName = (pathAndName.StartsWith(@"/") ? pathAndName.Substring(1) : pathAndName);

            throw new NotImplementedException();
        }
        public async Task<DropboxFileSystemItem> PostFile(Byte[] Data, String pathAndName, bool overwrite = false, String revisionTo = "", DropboxOAuthToken accessToken = null)
        {
            pathAndName = (pathAndName.StartsWith(@"/") ? pathAndName.Substring(1) : pathAndName);

            throw new NotImplementedException();
        }

        public async Task<DropboxFileSystemItem> GetFSItemMetaData(String path = "", String hash = "", bool list = true, int fileLimit = -1, bool includeDeleted = false, String revision = "", DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format(@"/1/metadata/{0}/{1}", rootFolderName, path);

            var parms = new Dictionary<String, String>();

            if (fileLimit > 0)
                parms["file_limit"] = fileLimit.ToString();

            if (!list)
                parms["list"] = list.ToString().ToLower();

            if (!String.IsNullOrEmpty(hash))
                parms["hash"] = hash;

            if (!String.IsNullOrEmpty(revision))
                parms["rev"] = revision;

            if (includeDeleted)
                parms["include_deleted"] = includeDeleted.ToString().ToLower();

            String result = await simpleHttpGet(baseurl, parms);
            if (!lastStatusWasOK())
                return null;
            Debug.WriteLine("MetaData: \n" + result);
            return DropboxFileSystemItem.Load(result);
        }

        public async Task<bool> DidFSITemChange(String path, String hash)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            var fsInfo = await GetFSItemMetaData(path, hash);
            return _lastResultCode == HttpStatusCode.NotModified;
        }

        public async Task<List<DropboxFileSystemItem>> GetFSItemRevisions(String path, int revLimit = -1, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format(@"/1/revisions/{0}/{1}", rootFolderName, path);

            var parms = new Dictionary<String, String>();

            if (revLimit > 0)
                parms["rev_limit"] = revLimit.ToString();

            String result = await simpleHttpGet(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            var items = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(result);

            var revisions = new List<DropboxFileSystemItem>();
            foreach (var item in items)
            {
                revisions.Add(DropboxFileSystemItem.Load(item.ToString()));
            }

            return revisions;
        }
        public async Task<DropboxFileSystemItem> RestoreRevision(String path, String revision, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format(@"/1/restore/{0}/{1}", rootFolderName, path);

            var parms = new Dictionary<String, String>();

            parms["rev"] = revision;

            String result = await simpleHttpPost(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileSystemItem.Load(result);
        }

        public async Task<List<DropboxFileSystemItem>> Search(String query, String path = "", int fileLimit = -1, bool includeDeleted = false, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format( @"/1/search/{0}/{1}", rootFolderName ,path);
            
            var parms = new Dictionary<String, String>();

            parms["query"]=query;

            if(fileLimit > 0 && fileLimit <=1000)
                parms["file_limit"] = fileLimit.ToString();
            
            if(includeDeleted)
                parms["include_deleted"] = includeDeleted.ToString().ToLower();

            String result = await simpleHttpGet(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            var items = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(result);

            var fileItems = new List<DropboxFileSystemItem>();
            foreach (var item in items)
	        {
		        fileItems.Add(DropboxFileSystemItem.Load(item.ToString()));
	        }

            return fileItems;
        }
        public async Task<DropboxFileLink> GetSharedLink(String path, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format(@"/1/shares/{0}/{1}", rootFolderName, path);

            String result = await simpleHttpPost(baseurl, new Dictionary<String, String>());
            if (!lastStatusWasOK())
                return null;

            return DropboxFileLink.Load(result);
        }
        
        public async Task<DropboxFileLink> GetMediaLink(String path, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format(@"/1/media/{0}/{1}", rootFolderName, path);

            String result = await simpleHttpPost(baseurl, new Dictionary<String, String>());
            if (!lastStatusWasOK())
                return null;
            
                return DropboxFileLink.Load(result);
            
            return null;
        }

        #endregion

        public async Task<DropboxFileSystemItem> Copy(String source, String dest, DropboxOAuthToken accessToken = null)
        {
            source = (source.StartsWith(@"/") ? source.Substring(1) : source);
            dest = (dest.StartsWith(@"/") ? dest.Substring(1) : dest);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/fileops/copy";

            var parms = new Dictionary<String, String>();
            parms["root"] = rootFolderName;
            parms["from_path"] = source;
            parms["to_path"] = dest;


            String result = await simpleHttpPost(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileSystemItem.Load(result);
        }

        public async Task<DropboxFileSystemItem> CreateFolder(String source, DropboxOAuthToken accessToken = null)
        {
            source = (source.StartsWith(@"/") ? source.Substring(1) : source);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/fileops/create_folder";

            var parms = new Dictionary<String, String>();
            parms["root"] = rootFolderName;
            parms["path"] = source;

            String result = await simpleHttpPost(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileSystemItem.Load(result);
        }

        public async Task<DropboxFileSystemItem> Delete(String source, DropboxOAuthToken accessToken = null)
        {
            source = (source.StartsWith(@"/") ? source.Substring(1) : source);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/fileops/delete";

            var parms = new Dictionary<String, String>();
            parms["root"] = rootFolderName;
            parms["path"] = source;

            String result = await simpleHttpPost(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileSystemItem.Load(result);
        }

        public async Task<DropboxFileSystemItem> Move(String source, String dest, DropboxOAuthToken accessToken = null)
        {
            source = (source.StartsWith(@"/") ? source.Substring(1) : source);
            dest = (dest.StartsWith(@"/") ? dest.Substring(1) : dest);
            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/fileops/move";

            var parms = new Dictionary<String, String>();
            parms["root"] = rootFolderName;
            parms["from_path"] = source;
            parms["to_path"] = dest;


            String result = await simpleHttpPost(baseurl, parms);
            if (!lastStatusWasOK())
                return null;
            
            return DropboxFileSystemItem.Load(result);
        }


        public async Task<DropboxDelta> Delta(String cursor, DropboxOAuthToken accessToken = null)
        {
            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/delta";

            var parms = new Dictionary<String, String>();
            parms["cursor"] = cursor;

            HttpRequestHelper.LargeResultReturning = true; // automatically resets itself (for WinRT)
            
            String result = await simpleHttpPost(baseurl, parms);

            if (!lastStatusWasOK())
                return null;

            return DropboxDelta.Load(result);
        }

        public async Task<DropboxFileLink> Copy_Ref(String path, DropboxOAuthToken accessToken = null)
        {
            path = (path.StartsWith(@"/") ? path.Substring(1) : path);
            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + String.Format(@"/1/copy_ref/{0}/{1}", rootFolderName, path);

            var parms = new Dictionary<String, String>();
            
            String result = await simpleHttpGet(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileLink.Load(result);
        }

        public async Task<DropboxFileSystemItem> File_Copy(DropboxFileLink copyRef, String dest, DropboxOAuthToken accessToken = null)
        {
            
            dest = (dest.StartsWith(@"/") ? dest.Substring(1) : dest);

            if (accessToken != null)
                AccessToken = accessToken;

            var baseurl = SECURE_API_HOST + @"/1/fileops/copy";

            var parms = new Dictionary<String, String>();
            parms["root"] = rootFolderName;
            parms["from_copy_ref"] = copyRef.CopyRef;
            parms["to_path"] = dest;

            String result = await simpleHttpPost(baseurl, parms);
            if (!lastStatusWasOK())
                return null;

            return DropboxFileSystemItem.Load(result);
        }
    }
}
